/*
 * ModelCC, distributed under ModelCC Shared Software License, www.modelcc.org
 */

package org.modelcc.language.factory;

import java.io.Serializable;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.modelcc.AssociativityType;
import org.modelcc.CompositionType;
import org.modelcc.Position;
import org.modelcc.SeparatorPolicy;
import org.modelcc.language.LanguageException;
import org.modelcc.language.LanguageSpecification;
import org.modelcc.language.lexis.*;
import org.modelcc.language.metamodel.*;
import org.modelcc.language.syntax.*;
import org.modelcc.language.syntax.builder.*;
import org.modelcc.lexer.recognizer.PatternRecognizer;
import org.modelcc.metamodel.Evaluator;

/**
 * Language Specification Factory.
 * 
 * @author Luis Quesada (lquesada@modelcc.org), refactored by Fernando Berzal (fberzal@modelcc.org)
 */
public class LanguageSpecificationFactory implements Serializable 
{
	// Specification factory
    private LexicalSpecificationFactory lsf = new LexicalSpecificationFactory();
    private SyntaxSpecificationFactory ssf = new SyntaxSpecificationFactory();
	private MemberSpecificationFactory msf = new MemberSpecificationFactory();
	
    // Mappings
    private Map<LanguageElement,SymbolIdentifier> identifiers = new HashMap<LanguageElement,SymbolIdentifier>();
    private Map<LanguageElement,RuleSymbol> symbols = new HashMap<LanguageElement,RuleSymbol>();
    private Map<LanguageElement,RuleSymbol> references = new HashMap<LanguageElement,RuleSymbol>();
    private Map<LanguageElement,Set<Rule>> symbolRules = new HashMap<LanguageElement,Set<Rule>>();
    private Map<LanguageElement,TokenSpecification> symbolTokens = new HashMap<LanguageElement,TokenSpecification>();
    private Map<PatternRecognizer,RuleSymbol> delimiters = new HashMap<PatternRecognizer,RuleSymbol>();
    private Map<PatternRecognizer,TokenSpecification> delimiterTokens = new HashMap<PatternRecognizer,TokenSpecification>();
    private Map<ListIdentifier,RuleSymbol> lists = new HashMap<ListIdentifier,RuleSymbol>();
    
    // Rules
    private Set<Rule> rules = new HashSet<Rule>();

    // Symbol builders
    private CompositeSymbolBuilder csb;
    private ReferenceSymbolBuilder rsb;
    private ReferenceSymbolPostBuilder prsb;
    private TokenSymbolBuilder tsb;        
    private TokenBuilder btb;
    private EmptySymbolBuilder esb;
    private DecoratorSymbolBuilder dsb;

    /**
     * Converts a model into a language specification
     * @param m the language model
     * @return the corresponding language specification
     * @throws LanguageException
     */
    public LanguageSpecification create(LanguageModel m) throws LanguageException 
    {
        createMaps(m);
        createSymbolBuilders(m);

        LexicalSpecification ls = createLexicalSpecification(m);
        SyntaxSpecification ss = createSyntaxSpecification(m);

        return new LanguageSpecification(ls,ss);
    }

	// Symbol builders

	private void createSymbolBuilders(LanguageModel m) 
	{
        csb = new CompositeSymbolBuilder(m);
        rsb = new ReferenceSymbolBuilder(m);
        prsb = new ReferenceSymbolPostBuilder(m);
        tsb = new TokenSymbolBuilder(m);        
        btb = new TokenBuilder(m);
        esb = new EmptySymbolBuilder(m);
        dsb = new DecoratorSymbolBuilder();
	}

	// Element maps
    
	private void createMaps(LanguageModel m) 
	{
		SymbolIdentifier id;
		
        for (LanguageElement el: m.getElements()) {
            id = new SymbolIdentifier(SymbolType.ELEMENT,el,null,false);
            identifiers.put(el,id);
            symbols.put(el, new RuleSymbol(id));
            id = new SymbolIdentifier(SymbolType.ELEMENT,el,null,true);
            references.put(el, new RuleSymbol(id));
        }
	}

	private Set<Rule> getRules(LanguageElement el) 
	{
		Set<Rule> sr = symbolRules.get(el);
		if (sr == null) {
		    sr = new HashSet<Rule>();
		    symbolRules.put(el,sr);
		}
		return sr;
	}
	
	// Lexical information
    // -------------------
	
	private LexicalSpecification createLexicalSpecification (LanguageModel m) 
		throws LanguageException
	{
        createDelimiters(m);

        // Language tokens
        
        for (LanguageElement el: m.getElements()) {
            if (SimpleLanguageElement.class.isAssignableFrom(el.getClass())) {

                SymbolIdentifier eid = identifiers.get(el);
                SymbolIdentifier beid = new SymbolIdentifier(SymbolType.BASIC,el,el.getSeparator(),false);

                Rule r = new Rule( new RuleSymbol(eid) );
                addDelimiters(r, el.getPrefix());
                r.add( new RuleSymbol(beid) );
                addDelimiters(r, el.getSuffix());
                r.setBuilder(dsb);

                Set<Rule> sr = getRules(el);

                ssf.addRule(r);
                sr.add(r);

                SimpleLanguageElement bel = (SimpleLanguageElement)el;

                // Hack: pattern matches empty string
                if (bel.getPatternRecognizer().read("",0)!=null) {
                	Rule er = new Rule( new RuleSymbol(eid) );
                	addDelimiters(er, el.getPrefix());
                	addDelimiters(er, el.getSuffix());
                	er.setBuilder(esb);
                	ssf.addRule(er);
                	sr.add(er);
                }
                
                TokenSpecification ts = new TokenSpecification(beid,bel.getPatternRecognizer(),btb);
                lsf.addTokenSpecification(ts);
                symbolTokens.put(bel,ts);
            }
        }
        
        return lsf.create(m);
	}

	private void addDelimiters (Rule r, List<PatternRecognizer> patterns) 
	{
		if (patterns!=null)
		    for (int i=0; i<patterns.size();i++)
		        r.add(delimiters.get(patterns.get(i)));
	}

	// Delimiters

	private void createDelimiters(LanguageModel m) 
	{
        for (PatternRecognizer pr: m.getDelimiters()) {
            TokenSpecification ts = new TokenSpecification(pr, pr);
        	lsf.addTokenSpecification(ts);
        	delimiterTokens.put(pr,ts);

            RuleSymbol re = new RuleSymbol(pr);
            // Hack: delimiter pattern matches empty string
            if (ts.getRecognizer().read("",0)!=null) {
            	RuleSymbol parent = new RuleSymbol(re); 
            	Rule er = new Rule(parent);
            	er.add(re);
            	er.setBuilder(null);
            	ssf.addRule(er);
            	Rule er2 = new Rule(parent);
            	er2.setBuilder(null);
            	ssf.addRule(er2);
            	delimiters.put(pr,parent);
            } else {
            	delimiters.put(pr,re);
            }
        }
	}	

    // Syntax information
    // ------------------

	private SyntaxSpecification createSyntaxSpecification (LanguageModel m) 
		throws LanguageException
	{
        ssf.setTokenSymbolBuilder(tsb);
        ssf.setStartType(identifiers.get(m.getStart()));
		
        createCompositeElements(m);
        createSelectionElements(m);

        createAssociativities(m);
        createPrecedences(m);
        
        // workaround: nullable lists cannot contain empty objects
        for (Rule r : rules) {
        	if (((SymbolIdentifier)r.getLeft().getType()).getType()!=SymbolType.LIST_ZERO) {
        		ssf.addNotEmpty(r.getLeft().getType());
        	}
        }
        
        return ssf.create(m);
	}

	// Composite elements

	private void createCompositeElements (LanguageModel m) 
	{
        for (LanguageElement element: m.getElements()) {
            RuleSymbol symbol = symbols.get(element);
            if (CompositeLanguageElement.class.isAssignableFrom(element.getClass()) && !Modifier.isAbstract(element.getElementClass().getModifiers())) {
            	
                CompositeLanguageElement ce = (CompositeLanguageElement)element;

                List<MemberSpecification> members = msf.create(ce);

                Set<Rule> sr = getRules(element);
                
                for (MemberSpecification spec: members)
                 	sr.addAll(createCompositeRule(m,ssf,symbol,spec,element,csb));

                for (Rule r: sr)
                    ssf.addRule(r);
                
                createPrecedences(ce, sr);
                
                if (!ce.getKeyMembers().isEmpty()) {
                    symbol = references.get(element);
                    for (List<LanguageMember> act: reference(ce)) {
                        ssf.addRule(createRule(m,symbol,act,null,null,rsb,prsb));
                    }
                }
            }
        }

        for (Rule rule: rules) {
            ssf.addRule(rule);
        }
	}


	// Precedences

	private void createPrecedences(CompositeLanguageElement ce, Set<Rule> sr) 
	{
		CompositionType ctyp = ce.getComposition();

		for (Rule r1 : sr) {
		    for (Rule r2 : sr) {
		    	if (r1 != r2) {
		    		boolean r1ssr2 = superset(r1,r2); 
		    		boolean r2ssr1 = superset(r2,r1); 
		    		switch (ctyp) {
		    		case EAGER:
		    			if (r1ssr2 && !r2ssr1) {
		    				ssf.addStartPrecedence(r1,r2);
		    				ssf.addCompositionPrecedence(r1,r2);
		    			}
		    			break;
		    		case LAZY:
		    			if (r1ssr2 && !r2ssr1) {
		    				ssf.addStartPrecedence(r2,r1);
		    				ssf.addCompositionPrecedence(r2,r1);
		    			}
		    			break;
		    		case EXPLICIT:
		    			ssf.addCompositionPrecedence(r2,r1);
		    			ssf.addCompositionPrecedence(r1,r2);
		    			break;
		    		case UNDEFINED:
		    			break;
		    		}
		    	}
		    }
		}
	}
		
    private boolean superset(Rule r1, Rule r2) 
    {
    	for (RuleSymbol elem : r2.getRight()) {
    		if (!r1.getRight().contains(elem))
    			return false;
    	}
    	return true;
	}	

	// Selector elements.
	
	private void createSelectionElements (LanguageModel m) 
	{
        for (LanguageElement el: m.getElements()) {
            SymbolIdentifier eid = identifiers.get(el);
            if (m.getSubelements().get(el) != null) {
            	for (LanguageElement el2: m.getSubelements().get(el)) {
            		SymbolIdentifier eid2 = identifiers.get(el2);
            		Rule r = new Rule( new RuleSymbol(eid));
            		addDelimiters(r, el.getPrefix());
            		r.add( new RuleSymbol(eid2) );
            		addDelimiters(r, el.getSuffix());
            		r.setBuilder(dsb);
            		ssf.addRule(r);

            		Set<Rule> sr = getRules(el);
            		sr.add(r);
            	}
            }
        }
	}

	// Associativities

	private void createAssociativities (LanguageModel m) 
	{
        for (LanguageElement el: m.getElements()) {
            SymbolIdentifier eid = identifiers.get(el);
            if (el.getAssociativity() != AssociativityType.UNDEFINED) {
            	ssf.setAssociativity(eid, el.getAssociativity());
            }
        }
	}

	// Precedences

	private void createPrecedences (LanguageModel m) 
	{
        for (LanguageElement el: m.getElements()) {
            if (CompositeLanguageElement.class.isAssignableFrom(el.getClass()) || !SimpleLanguageElement.class.isAssignableFrom(el.getClass())) {
                Set<Rule> sr = symbolRules.get(el);
                if (sr != null) {
                    if (m.getPrecedences().get(el) != null) {
                        for (LanguageElement el2: m.getPrecedences().get(el)) {
                            Set<Rule> sr2 = symbolRules.get(el2);
                            if (sr2 != null) {
                                for (Rule r1: sr) {
                                    for (Rule r2: sr2) {
                                        if (CompositeLanguageElement.class.isAssignableFrom(el.getClass()) && CompositeLanguageElement.class.isAssignableFrom(el2.getClass()))
                                            ssf.addCompositionPrecedence(r1, r2);
                                        if (!SimpleLanguageElement.class.isAssignableFrom(el.getClass()) && !SimpleLanguageElement.class.isAssignableFrom(el2.getClass()))
                                            ssf.addSelectionPrecedence(r1, r2);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
	}

    // Rules
	// -----
	
    private Set<Rule> createCompositeRule ( LanguageModel m, 
    		                                SyntaxSpecificationFactory ssf, 
    		                                RuleSymbol left,
    		                                MemberSpecification mn,
    		                                LanguageElement el,
    		                                CompositeSymbolBuilder csb) 
    {
        List<LanguageMember> elcs = mn.getMembers();
    	Set<Rule> ret = new HashSet<Rule>();
        int f = -1;
        boolean found = false;
        boolean err = false;

        if (elcs.size() > 2) {
        	for (int i=0; i<elcs.size(); i++) {
        		if (elcs.get(i).getElementClass().isAssignableFrom(el.getElementClass())) {
        			if (i>0) {
        				if (hasSubAsoc(m,m.getClassToElement().get(elcs.get(i-1).getElementClass()))) {
        					if (!found) found = true;
        					else if (i-1 != f) err = true;
        					f = i-1;
        				}
        			}
        			if (i<elcs.size()-1) {
        				if (hasSubAsoc(m,m.getClassToElement().get(elcs.get(i+1).getElementClass()))) {
        					if (!found) found = true;
        					else if (i+1 != f) err = true;
        					f = i+1;
        				}
        			}
        		}
        	}
        }
       
        if (err || !found) {
            ret.add(createRule(m,left,elcs,mn,el,csb));
        } else if (found) {            
            new HashMap<Integer,ArrayList<Rule>>();
            LanguageMember rep;
            Map<Rule,LanguageElement> rules = new HashMap<Rule,LanguageElement>();
            Map<Rule,Set<LanguageElement>> precedes = new HashMap<Rule,Set<LanguageElement>>();
            LanguageElement e = m.getClassToElement().get(elcs.get(f).getElementClass());
            for (LanguageElement sc: m.getSubelements().get(e)) {
                ArrayList<LanguageMember> elcc = new ArrayList<LanguageMember>();
                elcc.addAll(elcs);
                rep = elcc.get(f);
                elcc.remove(f);

                String field = rep.getID();
                boolean optional = rep.isOptional();
                List<PatternRecognizer> prefix = rep.getPrefix();
                List<PatternRecognizer> suffix = rep.getSuffix();
                List<PatternRecognizer> separator = rep.getSeparator();
                Class contentClass = sc.getElementClass();
                boolean id = rep.isKey();
                boolean reference = rep.isReference();
                Evaluator evaluator = rep.getEvaluator();

                LanguageMember ctx;

                if (rep.getClass().equals(MemberCollection.class)) {
                    MemberCollectionType collection = ((MemberCollection)rep).getCollection();
                    int minimumMultiplicity = ((MemberCollection)rep).getMinimumMultiplicity();
                    int maximumMultiplicity = ((MemberCollection)rep).getMaximumMultiplicity();
                    ctx = new MemberCollection(field,contentClass,optional,id,reference,prefix,suffix,separator,collection,minimumMultiplicity,maximumMultiplicity,evaluator);
                } else {
                    ctx = new LanguageMember(field,contentClass,optional,id,reference,prefix,suffix,separator,evaluator);
                }
                elcc.add(f,ctx);

                Rule r = createRule(m,left,elcc,mn,el,csb);
                ret.add(r);

                rules.put(r,m.getClassToElement().get(sc.getElementClass()));
                LanguageElement elrep = m.getClassToElement().get(sc.getElementClass());
                if (m.getPrecedences().get(elrep) != null) {
                    precedes.put(r,m.getPrecedences().get(elrep));
                }
            }

            for (Rule r1: rules.keySet()) {
                if (precedes.get(r1) != null) {
                    for (Rule r2: rules.keySet()) {
                        if (precedes.get(r1).contains(rules.get(r2))) {
                            ssf.addCompositionPrecedence(r1, r2);
                        }
                    }
                }
            }            
        }

        return ret;
    }

    private boolean hasSubAsoc(LanguageModel m,LanguageElement el) 
    {
        Set<LanguageElement> precededs = new HashSet<LanguageElement>();
        if (m.getSubelements().get(el) != null) {
            for (LanguageElement sc: m.getSubelements().get(el)) {
                if (m.getPrecedences().get(sc) != null)
                    precededs.addAll(m.getPrecedences().get(sc));
            }

            for (LanguageElement sc: m.getSubelements().get(el)) {
                if (precededs.contains(sc))
                    return true;
            }
        }
        return false;
    }

    private Rule createRule ( LanguageModel m, 
    		                  RuleSymbol left,
    		                  List<LanguageMember> cts,
    		                  MemberSpecification member,
    		                  LanguageElement el,
    		                  SymbolBuilder sb) 
    {
        return createRule(m,left,cts,member,el,sb,null);
    }
    
    private Rule createRule ( LanguageModel m,
    		                  RuleSymbol left,
    		                  List<LanguageMember> cts,
    		                  MemberSpecification member,
    		                  LanguageElement el,
    		                  SymbolBuilder sb,
    		                  SymbolBuilder psb) 
    {
        Rule r = new Rule(left);
        
        if (el != null)
        	addDelimiters(r, el.getPrefix());
        
        for (int i=0; i<cts.size();i++) {
        	MemberContent cm = null;
        	if (member != null)
        		cm = member.getContent(cts.get(i));
            addContent(m,r,cts.get(i),cm);
        }
        
        if (el != null)
            addDelimiters(r, el.getSuffix());

        r.setBuilder(sb);
        r.setPostBuilder(psb);
        return r;
    }

    private void addContent(LanguageModel m, Rule r, LanguageMember ct,MemberContent cm) 
    {
        LanguageElement el = m.getClassToElement().get(ct.getElementClass());
        
        addDelimiters(r, ct.getPrefix());

        if (!MemberCollection.class.isAssignableFrom(ct.getClass())) {
            if (ct.isReference()) {
                r.add(new RuleSymbol(references.get(el).getType(),ct));
            } else {
                r.add(new RuleSymbol(symbols.get(el).getType(),ct));
            }
        } else {
            r.add(listElement(m,ct,cm,ct.isReference()));
        }
        
        addDelimiters(r, ct.getSuffix());
    }    

 
    // References
    
	private Set<List<LanguageMember>> reference(CompositeLanguageElement ce) 
	{
		if (ce.isFreeOrder())
		    return referenceFreeOrder(ce.getKeyMembers(),new ArrayList<LanguageMember>());
		else
		    return referenceMembers(ce.getKeyMembers(),new ArrayList<LanguageMember>());
	}

	private Set<List<LanguageMember>> referenceFreeOrder(List<LanguageMember> elcs,List<LanguageMember> act) 
	{
        Set<List<LanguageMember>> ret = new HashSet<List<LanguageMember>>();
        List<LanguageMember> copy,actcopy;
        if (elcs.size() > 0) {
            for (int i=0; i<elcs.size(); i++) {
                copy = new ArrayList<LanguageMember>();
                copy.addAll(elcs);
                actcopy = new ArrayList<LanguageMember>();
                actcopy.addAll(act);
                actcopy.add(elcs.get(i));
                copy.remove(i);
                ret.addAll(referenceFreeOrder(copy,actcopy));
            }
        } else {
            ret.addAll(referenceMembers(act,new ArrayList<LanguageMember>()));
        }
        return ret;
    }

    private Set<List<LanguageMember>> referenceMembers(List<LanguageMember> elcs,List<LanguageMember> act) 
    {
        Set<List<LanguageMember>> ret = new HashSet<List<LanguageMember>>();
        ArrayList<LanguageMember> copy;
        ArrayList<LanguageMember> act2 = new ArrayList<LanguageMember>();
        act2.addAll(act);
        if (elcs.size() > 0) {
            copy = new ArrayList<LanguageMember>();
            copy.addAll(elcs);
            while (!copy.isEmpty()) {
                if (copy.get(0).isOptional()) {
                    act.add(copy.get(0));
                    copy.remove(0);
                    ret.addAll(referenceMembers(copy,act));
                    ret.addAll(referenceMembers(copy,act2));
                    return ret;
                } else {
                    act.add(copy.get(0));
                    act2.add(copy.get(0));
                    copy.remove(0);
                }
            }
            if (!act.isEmpty())
                ret.add(act);
        } else {
            ret.add(act);
        }
        return ret;
    }
	
    // List elements
    // -------------
    
    Map<LanguageElement,RuleSymbol> chosenSymbols;
    Map<LanguageElement,RuleSymbol> chosen;

    private RuleSymbol listElement(LanguageModel m,LanguageMember ct,MemberContent cm,boolean ref) 
    {
        List<PatternRecognizer> separator = null;

        LanguageElement el = m.getClassToElement().get(ct.getElementClass());
        
        if (ct.getSeparator() != null)
            separator = ct.getSeparator();
        else if (el.getSeparator() != null)
            separator = el.getSeparator();
        
    	int extraPos;
    
    	if (cm == null) {
        	extraPos = -1;
        	chosenSymbols = null;
        } else {
	        if (cm.getContent().isReference())
	        	chosenSymbols = references;
	        else
	        	chosenSymbols = symbols;

        	extraPos = cm.getPosition();
        }
    	
        if (ref)
        	chosen = references;
        else
        	chosen = symbols;

        if (extraPos == -1) {
            return listElementDefault(ct, ref, separator, m, el, cm);
        } else if (extraPos == Position.BEFORELAST) {
        	return listElementBeforeLast(ct, ref, separator, m, el, cm);
        } else { // if (pos == Position.WITHIN) {
            return listElementWithin(ct, ref, separator, m, el, cm);
        }
    }

    // Delimiter metadata
    
	class ListDecorator 
	{
		LanguageElement element;
    	int position;
    	SeparatorPolicy separatorPolicy;
    	RuleSymbol symbol;
        List<PatternRecognizer> prefix;
        List<PatternRecognizer> suffix;
	
        public ListDecorator (LanguageModel m, MemberContent cm)
        {
        	if (cm == null) {
            	element = null;
            	position = -1;
            	separatorPolicy = null;
            	prefix = null;
            	suffix = null;
            	symbol = null;
            } else {
            	LanguageMember member = cm.getContent();
            	element = m.getClassToElement().get(member.getElementClass());
            	position = cm.getPosition();
            	separatorPolicy = cm.getSeparatorPolicy();
            	prefix = member.getPrefix();
            	suffix = member.getSuffix();
            	symbol = new RuleSymbol(chosenSymbols.get(element).getType(),member);
            }        	
        }
	}

    // L -> E
	// L -> E L
	// L0 -> L
	// L0 -> epsilon

	private RuleSymbol listElementDefault(LanguageMember ct, boolean ref,
			List<PatternRecognizer> separator, LanguageModel m, LanguageElement el, MemberContent cm) 
	{		
		ListDecorator decorator = new ListDecorator(m,cm);		
        ListIdentifier l1 = new ListIdentifier(el,separator,ref,false,decorator.element,decorator.position,decorator.separatorPolicy,'1');
        ListIdentifier l0 = new ListIdentifier(el,separator,ref,true,decorator.element,decorator.position,decorator.separatorPolicy,'0');
        RuleSymbol re = lists.get(l1);
        RuleSymbol re0 = lists.get(l0);
		
		if (re == null) {
		    SymbolIdentifier id = new SymbolIdentifier(SymbolType.LIST,el,separator,ref);
		    re = new RuleSymbol(id);
		    lists.put(l1,re);

		    // L -> E
		    rules.add( ruleLE(el, re) );
		    // L -> E L
		    rules.add( ruleLEL(separator, el, re) );
		}
		
		if (((MemberCollection)ct).getMinimumMultiplicity()==0) {
		    if (re0 == null) {
		        SymbolIdentifier id = new SymbolIdentifier(SymbolType.LIST_ZERO,el,separator,ref);
		        re0 = new RuleSymbol(id);
		        lists.put(l0,re0);

		        // L0 -> L
		        rules.add(ruleL0L(re, re0));
		        // L0 -> epsilon
		        rules.add(ruleL0epsilon(re0));
		    }
		    return new RuleSymbol(re0.getType(),ct);
		} else {
		    return new RuleSymbol(re.getType(),ct);
		}
	}
	
	// L -> E lsep
	// L -> (sepPolicy:extra) E
	// Lsep -> sep E Lsep
	// Lsep -> (sepPolicy:extra) E

	private RuleSymbol listElementBeforeLast(LanguageMember ct, boolean ref,
			List<PatternRecognizer> separator, LanguageModel m, LanguageElement el, MemberContent cm) 
	{    
        ListDecorator decorator = new ListDecorator(m,cm);
        ListIdentifier ls = new ListIdentifier(el,separator,ref,false,decorator.element,decorator.position,decorator.separatorPolicy,'s');
        ListIdentifier lb = new ListIdentifier(el,separator,ref,false,decorator.element,decorator.position,decorator.separatorPolicy,'b');
        RuleSymbol res = lists.get(ls);
        RuleSymbol reb = lists.get(lb);

		if (res == null) {
		    SymbolIdentifier id = new SymbolIdentifier(SymbolType.LIST_SEP,el,separator,ref);
		    res = new RuleSymbol(id);
		    lists.put(ls,res);

			//Lsep -> sep E Lsep
		    rules.add(ruleLSsepELS(separator, el, res));
			//Lsep -> (sepPolicy:extra) E
		    rules.add(ruleLSsepE(separator, el, decorator, res));
		}

		if (reb == null) {
		    SymbolIdentifier id = new SymbolIdentifier(SymbolType.LIST_BEFORE_LAST,el,separator,ref);
		    reb = new RuleSymbol(id);
		    lists.put(lb,reb);

			//L -> E lsep
		    rules.add(ruleLEsep(el, res, reb));
			//L -> (sepPolicy:extra) E
		    rules.add(ruleLsepE(separator, el, decorator, reb));
		}

		RuleSymbol ro = new RuleSymbol(reb.getType(),ct);
		return ro;
	}
	
	// L -> E
	// L -> E L
	// Lw -> L (sepPolicy:extra) L
	// Lw -> (sepPolicy:extra) L
	// Lw -> L (sepPolicy:extra)
	// Lw -> (sepPolicy:extra)

	private RuleSymbol listElementWithin(LanguageMember ct, boolean ref,
			List<PatternRecognizer> separator, LanguageModel m, LanguageElement el, MemberContent cm)
	{	
		ListDecorator decorator = new ListDecorator(m,cm);
        ListIdentifier la = new ListIdentifier(el,separator,ref,false,decorator.element,decorator.position,decorator.separatorPolicy,'a');
        ListIdentifier lw = new ListIdentifier(el,separator,ref,false,decorator.element,decorator.position,decorator.separatorPolicy,'w');
        RuleSymbol rea = lists.get(la);
        RuleSymbol rew = lists.get(lw);

		if (rea == null) {
		    SymbolIdentifier id = new SymbolIdentifier(SymbolType.LIST_ELEMENT,el,separator,ref);
		    rea = new RuleSymbol(id);
		    lists.put(la,rea);

		    //L -> E
		    rules.add(ruleLE(el, rea));
		    // L -> E L
		    rules.add(ruleLEL(separator, el, rea));
		}
		
		if (rew == null) {
		    SymbolIdentifier id = new SymbolIdentifier(SymbolType.LIST_WITHIN,el,separator,ref);
		    rew = new RuleSymbol(id);
		    lists.put(lw,rew);

			//Lw -> L (sepPolicy:extra) L
		    rules.add(ruleLWLsepL(separator, decorator, rea, rew));
			//Lw -> (sepPolicy:extra) L
		    rules.add(ruleLWsepL(separator, decorator, rea, rew));
			//Lw -> L (sepPolicy:extra)
		    rules.add(ruleLWLsep(separator, decorator, rea, rew));
			//Lw -> (sepPolicy:extra)
		    rules.add(ruleLWsep(decorator.symbol, rew));
		}

		RuleSymbol ro = new RuleSymbol(rew.getType(),ct);
		return ro;
	}
	
	// Lw -> L (sepPolicy:extra) L

	private Rule ruleLWLsepL(List<PatternRecognizer> separator,
			ListDecorator decorator, RuleSymbol rea, RuleSymbol rew) 
	{
		Rule r = new Rule(rew);
		r.add(rea);
		addExtraSep(r, separator, decorator);
		r.add(rea);
		r.setBuilder(new ListListSymbolBuilder(0, r.getRight().size()-1));
		return r;
	}

	// Lw -> (sepPolicy:extra) L

	private Rule ruleLWsepL(List<PatternRecognizer> separator,
			ListDecorator decorator, RuleSymbol rea, RuleSymbol rew) {
		Rule r = new Rule(rew);
		addExtraSep(r, separator, decorator);
		r.add(rea);
		r.setBuilder(new ListSymbolBuilder(r.getRight().size()-1,-1));
		return r;
	}

	// Lw -> L (sepPolicy:extra)

	private Rule ruleLWLsep(List<PatternRecognizer> separator,
			ListDecorator decorator, RuleSymbol rea, RuleSymbol rew) {
		Rule r = new Rule(rew);
		r.add(rea);
		addExtraSep(r, separator, decorator);
		r.setBuilder(new ListSymbolBuilder(0,-1));
		return r;
	}

	// Lw -> (sepPolicy:extra)

	private Rule ruleLWsep(RuleSymbol extraRe, RuleSymbol rew) {
		Rule r = new Rule(rew);
		r.add(extraRe);
		r.setBuilder(new ListSymbolBuilder(-1,-1));
		return r;
	}

    // L -> E

	private Rule ruleLE(LanguageElement el, RuleSymbol rea) 
	{
		Rule r = new Rule(rea);
		r.add(chosen.get(el));
		r.setBuilder(new ListElementSymbolBuilder(0));
		return r;
	}
		
	// L -> E L
	
	private Rule ruleLEL(List<PatternRecognizer> separator, LanguageElement el, RuleSymbol re) {
		Rule r = new Rule(re);
		r.add(chosen.get(el));
		addDelimiters(r, separator);
		r.add(re);
		r.setBuilder(new ListSymbolBuilder(r.getRight().size()-1,0));
		return r;
	}

	// Lsep -> sep E Lsep

	private Rule ruleLSsepELS(List<PatternRecognizer> separator,
			LanguageElement el, RuleSymbol res) {
		Rule r = new Rule(res);
		addDelimiters(r, separator);
		r.add(chosen.get(el));
		r.add(res);
		r.setBuilder(new ListSymbolBuilder(r.getRight().size()-1,r.getRight().size()-2));
		return r;
	}
	
	// Lsep -> (sepPolicy:extra) E

	private Rule ruleLSsepE(List<PatternRecognizer> separator,
			LanguageElement el, ListDecorator metadata, RuleSymbol res) {
		Rule r = new Rule(res);
		addExtraSep(r, separator, metadata);		
		r.add(chosen.get(el));
		r.setBuilder(new ListElementSymbolBuilder(r.getRight().size()-1));
		return r;
	}

	// L -> E lsep

	private Rule ruleLEsep(LanguageElement el, RuleSymbol res, RuleSymbol reb) {
		Rule r = new Rule(reb);
		r.add(chosen.get(el));
		r.add(res);
		r.setBuilder(new ListSymbolBuilder(r.getRight().size()-1,0));
		return r;
	}

	// L -> (sepPolicy:extra) E

	private Rule ruleLsepE(List<PatternRecognizer> separator,
			LanguageElement el, ListDecorator decorator, RuleSymbol reb) 
	{
		Rule r = new Rule(reb);
		addExtraSep(r, separator, decorator);
		r.add(chosen.get(el));
		r.setBuilder(new ListElementSymbolBuilder(r.getRight().size()-1));
		return r;
	}

	private void addExtraSep(Rule r, List<PatternRecognizer> separator, ListDecorator decorator) 
	{
		switch (decorator.separatorPolicy) {
		case AFTER:
			addDelimiters(r, separator);
			addDelimiters(r, decorator.prefix);
		    r.add(decorator.symbol);
		    addDelimiters(r, decorator.suffix);
			break;
		case BEFORE:
			addDelimiters(r, decorator.prefix);
		    r.add(decorator.symbol);
		    addDelimiters(r, decorator.suffix);
		    addDelimiters(r, separator);
			break;
		case EXTRA:
			addDelimiters(r, separator);
			addDelimiters(r, decorator.prefix);
		    r.add(decorator.symbol);
		    addDelimiters(r, decorator.suffix);
		    addDelimiters(r, separator);
			break;
		case REPLACE:
			addDelimiters(r, decorator.prefix);
		    r.add(decorator.symbol);
		    addDelimiters(r, decorator.suffix);
			break;
		}
	}

    // L0 -> L

	private Rule ruleL0L(RuleSymbol re, RuleSymbol re0) {
		Rule r = new Rule(re0);
		r.add(re);
		r.setBuilder(new SymbolBuilder(){
		    @Override
			public boolean build(Symbol t,ParserMetadata data) {
		        t.setUserData(t.getContent(0).getUserData());
		        return true;
		    }
		});
		return r;
	}

    // L0 -> epsilon

	private Rule ruleL0epsilon(RuleSymbol re0) {
		Rule r = new Rule(re0);
		r.setBuilder(new SymbolBuilder(){
		    @Override
			public boolean build(Symbol t, ParserMetadata data) {
		        t.setUserData(new ListContent(new Object[0]));
		        return true;
		    }
		});
		return r;
	}
}